package com.danbay.cloud.account.service;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.danbay.cloud.account.dto.AccountGroupDto;
import com.danbay.cloud.account.client.SmsServiceClient;
import com.danbay.cloud.account.config.AccountProperties;
import com.danbay.cloud.account.dto.AccountDto;
import com.danbay.cloud.account.dto.ChangePwd;
import com.danbay.cloud.account.dto.FindPwdDto;
import com.danbay.cloud.account.entity.Account;
import com.danbay.cloud.account.entity.AccountRole;
import com.danbay.cloud.account.entity.Employee;
import com.danbay.cloud.account.entity.Enterprise;
import com.danbay.cloud.account.mapper.*;
import com.danbay.cloud.account.service.api.IAccountService;
import com.danbay.cloud.base.constants.Roles;
import com.danbay.cloud.base.user.LoginUserInfo;
import com.danbay.framework.exception.BusinessException;
import com.danbay.framework.utils.BeanUtils;
import com.danbay.framework.utils.BeanValidator;
import com.danbay.framework.utils.RandomUtils;
import com.danbay.framework.utils.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author SJG
 * @date 2017/10/9
 */
@Service
public class AccountService implements IAccountService {

    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private EnterpriseMapper enterpriseMapper;
    @Autowired
    private AccountGroupMapper accountGroupMapper;
    @Autowired
    private AccountRoleMapper accountRoleMapper;
    @Autowired
    private EmployeeMapper employeeMapper;

    @Autowired
    private SmsServiceClient smsServiceClient;

    @Autowired
    private AccountProperties accountProperties;

    @Autowired
    private LoginUserService loginUserService;

    private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    /**
     * 使用用户名取得账户信息
     *
     * @param userName 用户名
     * @return 账户信息
     */
    @Override
    public AccountDto get(String userName) {
        if (StringUtils.isBlank(userName)) {
            return null;
        }
        return BeanUtils.convert(accountMapper.selectById(userName), AccountDto.class);
    }

    /**
     * 添加账户
     * 密码为空时将使用默认密码
     *
     * @param accountDto 账户信息
     * @throws BusinessException 业务异常
     */
    @Override
    public void addAccount(AccountDto accountDto) throws BusinessException {
        BeanValidator.valid(accountDto);
        checkExists(accountDto.getUserName());
        checkExistsPhoneNum(accountDto.getPhoneNum());
        if (accountGroupMapper.selectById(accountDto.getAccountGroup()) == null) {
            throw new BusinessException("账户组不存在");
        }

        Account account = Account.builder()
                .userName(accountDto.getUserName())
                .phoneNum(accountDto.getPhoneNum())
                .password(passwordEncoder.encode(accountDto.getPassword() == null ? accountProperties.getDefaultPassword() : accountDto.getPassword()))
                .nickName(accountDto.getNickName() == null ? accountDto.getUserName() : accountDto.getNickName())
                .accountNonExpired(true)
                .accountNonLocked(true)
                .credentialsNonExpired(true)
                .del(false)
                .enabled(true)
                .groupIds("," + String.valueOf(accountDto.getAccountGroup()) + ",")
                .addTime(DateTime.now().toDate())
                .build();
        accountMapper.insert(account);
    }

    /**
     * 检查是否存在账户
     *
     * @param name 账户名
     */
    @Override
    public void checkExists(String name) throws BusinessException {
        if (accountMapper.selectById(name) != null) {
            throw new BusinessException("账户已存在");
        }
    }

    /**
     * 检查是否已存在企业账户电话号码
     * 同一个电话号码只能绑定一个企业账户
     *
     * @param phoneNum 电话号码
     * @return 是否存在
     */
    @Override
    public void checkExistsPhoneNum(String phoneNum) throws BusinessException {
        if (accountMapper.selectCount(new EntityWrapper(Account.builder().phoneNum(phoneNum).build())) > 0) {
            throw new BusinessException("电话号码已存在");
        }
    }

    /**
     * 检查账户不存在
     *
     * @param name 账户名
     */
    public Account checkNotExists(String name) throws BusinessException {
        Account account = accountMapper.selectById(name);
        if (account == null) {
            throw new BusinessException("账户不存在");
        }
        return account;
    }

    /**
     * 冻结账户
     *
     * @param userName 账户名
     */
    @Override
    public void freeze(String userName) throws BusinessException {
        checkNotExists(userName);
        accountMapper.updateById(Account.builder().userName(userName).enabled(false).build());
    }

    /**
     * 解冻账户
     *
     * @param userName 账户名
     */
    @Override
    public void unfreeze(String userName) throws BusinessException {
        checkNotExists(userName);
        accountMapper.updateById(Account.builder().userName(userName).enabled(true).build());
    }

    /**
     * 修改账户密码
     *
     * @param changePwd 修改密码信息
     * @throws BusinessException 业务异常
     */
    @Override
    public void changePwd(ChangePwd changePwd) throws BusinessException {
        BeanValidator.valid(changePwd);
        Account account = checkAccountNotExist(changePwd.getUserName());
        if (!passwordEncoder.matches(changePwd.getOldPwd(), account.getPassword())) {
            throw new BusinessException("原密码不正确");
        }
        int r = accountMapper.updateById(Account.builder()
                .userName(account.getUserName())
                .password(passwordEncoder.encode(changePwd.getNewPwd()))
                .build());
        if (r == 0) {
            throw new BusinessException("修改密码失败");
        }
    }

    /**
     * 取消账户隶属组
     *
     * @param userName 账号
     * @param groupId  账户组
     * @throws BusinessException 业务异常
     */
    @Override
    public void cancelGroup(String userName, Integer groupId) throws BusinessException {
        Account account = checkAccountNotExist(userName);
        if (StringUtils.isBlank(account.getGroupIds()) || groupId == null) {
            throw new BusinessException("groupId不能为空或该用户账户组为空");
        }
        final String[] groupIds = {""};
        Arrays.stream(account.getGroupIds().split(",")).forEach(
                (String id) -> groupIds[0] += !id.contentEquals(groupId.toString()) ? id + "," : "");
        account.setGroupIds(groupIds[0]);
        accountMapper.updateById(account);
    }

    /**
     * 用户登录
     * 此处的登录只是标记用户当前是以哪个账户组登录的，真正的登录是走获取token接口，如果该账户有多种角色需要指定以哪种角色登录
     *
     * @param userName 账号
     * @param groupId  账户组
     * @param role     角色
     * @throws BusinessException 业务异常
     */
    @Override
    public LoginUserInfo login(String userName, Integer groupId, String role) throws BusinessException {
        Account account = checkNotExists(userName);
        if (groupId == null) {
            throw new BusinessException("账户组id不能为空");
        }
        if (accountGroupMapper.selectById(groupId) == null) {
            throw new BusinessException("该账户组不能存在");
        }
        if (accountMapper.selectCount(new EntityWrapper<>(Account.builder().userName(userName).del(false).enabled(true).build())) == 0) {
            throw new BusinessException("该账户已被冻结");
        }

        List<AccountRole> roles = accountRoleMapper.selectList(new EntityWrapper<>(AccountRole.builder().userName(userName).build()));
        if (StringUtils.isNotBlank(role) && !roles.contains(new AccountRole(userName, role))) {
            throw new BusinessException("角色错误");
        }
        role = role == null ? roles.get(0).getRoleName() : role;
        //todo 根据账户组和角色判断是否可以登录
        //如果是设备管理系统
        if (true) {
            validateDeviceManageSystemAccountLogin(userName, groupId, role);
        }

        return loginUserService.cacheLoginUserInfo(account, groupId, role);
    }

    /**
     * 验证设备管理系统账户登录
     *
     * @param userName 账号
     * @param groupId  账户组
     * @param role     角色
     */
    private void validateDeviceManageSystemAccountLogin(String userName, Integer groupId, String role) throws BusinessException {
        Roles roleType = Roles.parse(role);
        if (roleType == Roles.ENTERPRISE) {
            //企业账户
            if (enterpriseMapper.selectCount(new EntityWrapper<>(Enterprise.builder().bindAccount(userName).del(false).freeze(false).build())) == 0) {
                throw new BusinessException("该账户所在的企业已经被删除或被冻结");
            }
        } else if (roleType == Roles.EMPLOYEE) {
            //员工
            //取员工所在企业
            Enterprise enterprise = enterpriseMapper.selectOne(Enterprise.builder().accountGroupId(groupId).build());
            if (enterprise == null || enterprise.getDel() || enterprise.getFreeze()) {
                throw new BusinessException("该账户所在的企业已经被删除或被冻结");
            }

            if (employeeMapper.selectCount(new EntityWrapper<>(Employee.builder()
                    .enterpriseId(enterprise.getId()).bindAccount(userName).del(false).freeze(false)
                    .build())) == 0) {
                throw new BusinessException("该账户绑定的员工已经被删除或被冻结");
            }
        }
    }

    /**
     * 取已登录了的用户信息
     *
     * @param userName 账号
     * @return 用户信息
     */
    @Override
    public LoginUserInfo getLoginUserInfo(String userName) {
        return loginUserService.getLoginUserInfo(userName);
    }

    /**
     * 更新缓存的用户登录信息
     *
     * @param userInfo 用户登录信息
     */
    public void updateCachedLoginUserInfo(LoginUserInfo userInfo) {
        loginUserService.updateCachedLoginUserInfo(userInfo);
    }

    /**
     * 获取账户所在账户组列表
     *
     * @param userName 账号
     * @return 账户组列表
     */
    @Override
    public List<AccountGroupDto> getAccountGroups(String userName) throws BusinessException {
        Account account = checkAccountNotExist(userName);
        if (StringUtils.isBlank(account.getGroupIds())) {
            throw new BusinessException("该账户不属于任何账户组");
        }
        String[] groups = account.getGroupIds().split(",");
        if (groups.length == 0) {
            return null;
        }
        return BeanUtils.convert(accountGroupMapper.selectBatchIds(Arrays.asList(groups)), AccountGroupDto.class);
    }

    /**
     * 重置密码
     * 重置后会向该账户的手机发送一个随机密码
     *
     * @param userName 账号
     * @throws BusinessException 业务异常
     */
    @Override
    public void resetPwd(String userName) throws BusinessException {
        Account account = checkAccountNotExist(userName);
        if (StringUtils.isBlank(account.getPhoneNum())) {
            throw new BusinessException("该账户手机号为空");
        }
        String newPwd = RandomUtils.randomString(6);
        account.setPassword(passwordEncoder.encode(newPwd));
        accountMapper.updateById(account);
        //发送密码 todo 临时用验证码接口发短信
        smsServiceClient.sendCheckCode(account.getPhoneNum(), newPwd, 300);
    }

    /**
     * 发送找回密码短信，有效期1分钟
     *
     * @param mobile 手机号
     * @return 验证码id
     * @throws BusinessException 业务异常
     */
    @Override
    public String sendFindPwdMessage(String mobile) throws BusinessException {
        if (accountMapper.selectCount(new EntityWrapper<>(Account.builder().phoneNum(mobile).del(false).enabled(true).build())) <= 0) {
            throw new BusinessException("该手机对应的账户不存在或已被禁用");
        }
        return smsServiceClient.sendFindPwdCheckCode(mobile, RandomUtils.randomString(6), 60);
    }

    /**
     * 先调用发送找回密码短信，再填写验证码提交找回密码
     *
     * @param findPwdDto 找回密码信息
     * @throws BusinessException 业务异常
     */
    @Override
    public void findPwd(FindPwdDto findPwdDto) throws BusinessException {
        BeanValidator.valid(findPwdDto);

        List<Account> accounts = accountMapper.selectList(new EntityWrapper<>(Account.builder().phoneNum(findPwdDto.getMobile()).del(false).enabled(true).build()));
        if (accounts == null || accounts.size() == 0) {
            throw new BusinessException("该手机对应的账户不存在或已被禁用");
        }
        Account account = accounts.get(0);
        //更新密码
        account.setPassword(passwordEncoder.encode(findPwdDto.getNewPwd()));
        accountMapper.updateById(account);
    }

    /**
     * 取账户角色列表
     *
     * @param userName 用户名
     * @return 账户角色列表
     */
    public Set<String> getAccountRoles(String userName) {
        return accountRoleMapper.selectList(new EntityWrapper<>(AccountRole.builder().userName(userName).build()))
                .stream().map(AccountRole::getRoleName).collect(Collectors.toSet());
    }

    /**
     * 检查账户是否存在
     *
     * @param userName 账户名
     * @return 账户信息
     * @throws BusinessException 业务异常
     */
    public Account checkAccountNotExist(String userName) throws BusinessException {
        if (userName == null) {
            throw new BusinessException("账户不存在");
        }
        Account account = accountMapper.selectById(userName);
        if (account == null) {
            throw new BusinessException("账户不存在");
        }
        return account;
    }
}